Skip to content

iOS: distinct sticky-lock cue for glass toolbar modifiers (double-tap lock vs one-shot arm) - #5604

Closed
lawrencecchen wants to merge 1 commit into
feat-ios-toolbar-reorder-builtinsfrom
feat-ios-glass-sticky-lock-cue
Closed

lawrencecchen wants to merge 1 commit into
feat-ios-toolbar-reorder-builtinsfrom
feat-ios-glass-sticky-lock-cue

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented Jun 8, 2026 •

Copy link
Copy Markdown
Contributor

On iOS 26 the Liquid Glass accessory bar (#5536 / #5579) made the one-shot-armed and double-tap sticky-locked modifier states render identically: both used .prominentGlass() with a .systemBlue tint, so you could not tell a locked modifier from an armed one. The pre-iOS-26 flat style had always distinguished the locked state with a 2pt white stroke; that cue was lost on the glass path. Reported in dogfood: double-tapping a modifier to sticky-lock it showed no visible difference.

This restores the locked cue as a white capsule border drawn on the button's layer, over the glass. AccessoryActionButton gains an isStickyLocked flag that syncs a layer-level border (cornerRadius = bounds.height / 2, .continuous curve, white, 2pt) from didSet and layoutSubviews. Drawing at the layer level (not via UIButton.Configuration.background.strokeColor) composites the border on top of Liquid Glass regardless of how the glass material renders its own background, so the cue reads clearly over glass. It mirrors the pre-26 white stroke, and adds zero intrinsic width so it does not fight the bar's min-width sizing. The border is set unconditionally from sticky, so a button going locked then armed then resting never keeps a stale border. It is applied only on iOS 26; the pre-26 flat path keeps its existing background-config stroke (no doubled border).

Three states now read distinctly:

  • resting: plain .glass()
  • one-shot-armed (single tap): .prominentGlass() + blue, no border
  • sticky-locked (double tap): .prominentGlass() + blue + white capsule border

Dogfood: single-tap Ctrl shows the armed look (blue glass, no border); double-tap Ctrl shows the clearly-locked look (blue glass with a white capsule border around it). The simulator may not composite the glass material, so the locked-over-glass read is best confirmed on device.

Branching and merge sequencing: this is based off feat-ios-toolbar-reorder-builtins (#5579), which carries the glass styling being fixed. Merge #5579 first, then this. The in-flight button-min-width PR #5599 (feat-ios-accessory-min-width) edits the exact same two functions on the same line, so it overlaps and will conflict on merge: both touch accessoryButtonConfiguration and applyAccessoryButtonStyle in TerminalInputTextView.swift. The border cue here was deliberately chosen to add zero width so it composes with #5599's narrower buttons; sequence #5599 and this PR together and resolve the trivial overlap in applyAccessoryButtonStyle.

Verified: clean iOS simulator Debug build (cmux-ios scheme, isolated derivedDataPath). Not a device build, no xcodebuild test. Autoreview clean.

🤖 Generated with Claude Code


View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.


Note

Low Risk
Localized UI-only styling in the mobile terminal accessory bar; no input or modifier state logic changes.

Overview
On iOS 26, sticky-locked modifier keys (double-tap) were visually indistinguishable from one-shot armed keys because both used the same prominent Liquid Glass blue fill. This PR restores a 2pt white capsule border on the button layer for the sticky-lock state only, matching the cue pre-26 flat styling already had via background stroke.

AccessoryActionButton gains isStickyLocked, which toggles the layer border in didSet and keeps the capsule aligned in layoutSubviews. applyAccessoryButtonStyle sets that flag from the existing sticky state on iOS 26+ and forces it off on earlier OS versions so the flat background stroke is not doubled.

Resting, armed, and sticky-locked modifiers should now read as three distinct states on glass toolbars.

Reviewed by Cursor Bugbot for commit d3a82dc. Bugbot is set up for automated code reviews on this repo. Configure here.


Summary by cubic

Fixes the iOS 26 Liquid Glass toolbar so sticky‑locked modifiers show a clear white capsule border, making them distinct from one‑shot armed. Pre‑iOS‑26 styling stays the same.

  • Bug Fixes

    • Added isStickyLocked to AccessoryActionButton to sync a 2pt white layer border (cornerRadius = height/2, continuous curve) and clear it when unlocked.
    • Border updates in didSet and layoutSubviews; applied only on iOS 26. Earlier iOS keeps the existing background stroke.
    • Visual states: resting (glass), armed (prominent glass + blue), locked (prominent glass + blue + white border).
  • Migration

    • Merge feat-ios-toolbar-reorder-builtins first. Expect a small overlap with feat-ios-accessory-min-width in applyAccessoryButtonStyle and accessoryButtonConfiguration.

Written for commit d3a82dc. Summary will update on new commits.

Review in cubic

On iOS 26 the Liquid Glass accessory bar (#5536/#5579) rendered the
one-shot-armed and double-tap sticky-locked modifier states identically:
both used .prominentGlass() with a systemBlue tint, so a locked modifier
was indistinguishable from an armed one. The pre-26 flat style had always
marked the locked state with a 2pt white stroke; that distinction was lost
on the glass path.

Restore the locked cue as a white capsule border drawn on the button's
layer, over the glass. AccessoryActionButton gains an isStickyLocked flag
that syncs a layer-level border (cornerRadius = height/2, .continuous,
white, 2pt) in didSet and layoutSubviews. Drawing at the layer level
composites on top of Liquid Glass regardless of how the glass material
renders its background, matches the pre-26 white stroke, and adds zero
intrinsic width so it does not fight the bar's min-width sizing. The border
is set unconditionally from `sticky` so a button that goes locked -> armed
-> resting never keeps a stale border. Applied only on iOS 26; the pre-26
flat path keeps its existing background-config stroke (no doubled border).

Three states now read distinctly: resting (plain glass), one-shot-armed
(prominent glass + blue), sticky-locked (prominent glass + blue + white
capsule border).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 8, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment Jun 8, 2026 3:40am
cmux-staging Building Building Preview, Comment Jun 8, 2026 3:40am

@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 71f94bab-3b53-4ade-b53b-99c641b0964b

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat-ios-glass-sticky-lock-cue

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps

greptile-apps Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Restores a distinct visual cue for the double-tap sticky-locked modifier state on iOS 26, where the Liquid Glass accessory bar made the armed and locked states indistinguishable. A white 2pt capsule border is drawn on the button's root CALayer — above the glass material — matching the pre-26 flat-style stroke.

  • AccessoryActionButton gains an isStickyLocked flag whose didSet and layoutSubviews both call updateStickyLockBorder, which sets or clears layer.cornerRadius, layer.borderColor, and layer.borderWidth to render the capsule border.
  • applyAccessoryButtonStyle in TerminalInputTextView wires sticky into isStickyLocked behind an #available(iOS 26.0, *) guard, leaving the pre-26 flat path unchanged.

Confidence Score: 4/5

Safe to merge with one small fix: the unlock branch needs to reset cornerRadius and cornerCurve back to their defaults.

The feature logic and iOS-version gating are correct. The one concrete issue is updateStickyLockBorder's else branch: it clears borderWidth/borderColor but leaves layer.cornerRadius and layer.cornerCurve at their locked values. Any button that has ever been sticky-locked carries a stale capsule radius for the rest of its lifetime, which would cause visible clipping if UIKit's glass configuration sets masksToBounds on the root layer now or in a future OS update.

Packages/CmuxMobileTerminal/Sources/CmuxMobileTerminal/AccessoryActionButton.swift — the updateStickyLockBorder unlock path

Important Files Changed

Filename Overview
Packages/CmuxMobileTerminal/Sources/CmuxMobileTerminal/AccessoryActionButton.swift Adds isStickyLocked flag and layer-level capsule border for iOS 26 locked state; the else branch of updateStickyLockBorder clears border properties but leaves layer.cornerRadius/layer.cornerCurve stale after an unlock transition.
Packages/CmuxMobileTerminal/Sources/CmuxMobileTerminal/TerminalInputTextView.swift Wires isStickyLocked into applyAccessoryButtonStyle, guarded to iOS 26+ only and always reset to false on older OSes; logic is correct and matches the rest of the style-application pattern.

Reviews (1): Last reviewed commit: "iOS: distinct sticky-lock cue for glass ..." | Re-trigger Greptile

Comment on lines +68 to +71
} else {
layer.borderWidth = 0
layer.borderColor = nil
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Stale cornerRadius and cornerCurve after unlocking — the else branch clears the border properties but never resets layer.cornerRadius or layer.cornerCurve. Once a button transitions from locked → armed → resting, the root CALayer permanently retains a capsule cornerRadius. If UIKit's glass configuration ever sets masksToBounds on the root layer (or if a future UIKit update does), the stale radius would clip the glass content to a capsule shape for the button's lifetime.

Suggested change
} else {
layer.borderWidth = 0
layer.borderColor = nil
}
} else {
layer.borderWidth = 0
layer.borderColor = nil
layer.cornerRadius = 0
layer.cornerCurve = .circular
}

Rule Used: Flag Swift fixes that patch symptoms while leaving... (source)

@lawrencecchen lawrencecchen added the stale-revisit Closed after 30+ days without activity; preserved for possible revisit or reopening. label Sep 23, 2026
@github-project-automation github-project-automation Bot moved this from Todo to Done in cmux backlog Sep 23, 2026

This branch was successfully deployed

1 active deployment
Preview – cmux — d3a82dcf Deployed Jun 8, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

stale-revisit Closed after 30+ days without activity; preserved for possible revisit or reopening.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants